﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Com.CloudTalk.Data;
using System.Threading;
using System.Collections.Specialized;
using System.Collections;

namespace Com.CloudTalk.Controller
{
    public class CloudTalkController
    {
        /// <summary>
        /// Dictionary for keeping a value indicating whether we have new events ready for each client,
        /// with each element being a pair [client email, event available].
        /// </summary>
        private static Dictionary<string, AutoResetEvent> dictEvents = new Dictionary<string, AutoResetEvent>();

        /// <summary>
        /// Dictionary for keeping a queue of waiting clients for each chat room (pairs of [room id; client email list]).
        /// </summary>
        private static Dictionary<string, Queue<WaitingClient>> dictQueues = new Dictionary<string, Queue<WaitingClient>>();

        internal class WaitingClient
        {
            public string Name { get; set; }
            public string Email { get; set; }
        }
        
        public CloudTalkController()
        {
        }

        #region Utility methods

        /// <summary>
        /// Returns the client that matches the provided email address. 
        /// Searches also in the room queues.
        /// If no one is found, null is returned
        /// </summary>
        /// <param name="_sEmail">The email address</param>
        /// <returns>The instance of the client</returns>
        public static CTClient GetClientByEmail(string _sEmail)
        {
            Client c = CTClient.GetClientByEmail(_sEmail);

            if (c != null)
            {
                return new CTClient(c.Name, c.Email);
            }

            // lookup into the room queues
            foreach (string key in dictQueues.Keys)
            {
                WaitingClient waitingClnt = dictQueues[key].FirstOrDefault(clnt => clnt.Email == _sEmail);
                if (waitingClnt != null)
                {
                    // found the client in one of the room queues
                    return new CTClient(waitingClnt.Name, waitingClnt.Email);
                }
            }

            // the client was not found at all
            return null;
        }

        /// <summary>
        /// Creates a new chat room
        /// </summary>
        /// <param name="_sRoomName">The name for the new room</param>
        /// <returns>The instance of the just created chat room</returns>
        public static CTChatRoom CreateChatRoom(string _sRoomName)
        {
            return CreateChatRoom(_sRoomName, -1);
        }

        /// <summary>
        /// Creates a new chat room
        /// </summary>
        /// <param name="_sRoomName">The name for the new room</param>
        /// <param name="_nMaxUsers">The maximum number of clients allowed in this room</param>
        /// <returns>The instance of the just created chat room</returns>
        public static CTChatRoom CreateChatRoom(
            string _sRoomName,
            int _nMaxUsers)
        {
            using (CloudTalkEntities db = new CloudTalkEntities())
            {
                ChatRoom c = new ChatRoom()
                {
                    Name = _sRoomName,
                    RequiresAuthentication = false,
                    MaxUsers = _nMaxUsers
                };

                db.AddToChatRoom(c);
                db.SaveChanges();

                return new CTChatRoom(_sRoomName, c.ID);
            }
        }

        /// <summary>
        /// Opens an existing chat room with the specificed name or creates
        /// a new one if it does not exist yet
        /// </summary>
        /// <param name="_sRoomName">The room name</param>
        /// <returns>The chat room object</returns>
        public static CTChatRoom OpenChatRoom(string _sRoomName)
        {
            using (CloudTalkEntities db = new CloudTalkEntities())
            {

                ChatRoom room = db.ChatRoom
                    .Where(r => r.Name == _sRoomName)
                    .FirstOrDefault();

                if (room != null)
                {
                    CTChatRoom c = new CTChatRoom(room.ID);
                    c.Name = _sRoomName;
                    return c;
                }
                else
                {
                    return CreateChatRoom(_sRoomName);
                }
            }
        }

        /// <summary>
        /// Deletes all chat rooms, clients and pending events from the system
        /// </summary>
        public static void ClearTables()
        {
            using (CloudTalkEntities db = new CloudTalkEntities())
            {
                db.ClearTable(db.History);
                db.ClearTable(db.Event);
                db.ClearTable(db.Client);
                db.ClearTable(db.ChatRoom);
            }
        }

        /// <summary>
        /// Releases all waiting threads on the provided client email
        /// </summary>
        /// <param name="_client">The client that will be checked</param>
        /// <returns>True if the client has pending events, otherwise, false</returns>
        private static void SignalClient(string _email)
        {
            if (!dictEvents.ContainsKey(_email))
            {
                dictEvents[_email] = new AutoResetEvent(true);  // initialize the event as signalled
            }
            else
            {
                AutoResetEvent autoEvent = dictEvents[_email];
                autoEvent.Set();                                // signal and allow the waiting threads to proceed
            }
        }

        public static bool WaitForEvent(string _sEmail, int _nTimeout)
        {
            AutoResetEvent autoEvent;

            if (!dictEvents.ContainsKey(_sEmail))
            {
                autoEvent = new AutoResetEvent(false);  // initialize the event as non-signalled
                dictEvents[_sEmail] = autoEvent;
            }
            else
            {
                autoEvent = dictEvents[_sEmail];
            }
            
            return autoEvent.WaitOne(_nTimeout);        // block the current thread
        }

        #endregion

        #region Chat room operations

        public static int RoomQueueSize(CTChatRoom _room)
        {
            return (dictQueues.ContainsKey(_room.Name))
                ? dictQueues[_room.Name].Count
                : 0;
        }

        public static CTClient AddClient(
            CTChatRoom _room,
            string _sName,
            string _sEmail,
            RoleTypes _eRole)
        {
            try
            {
                CTClient newClient = _room.AddClient(_sName, _sEmail, _eRole);

                RegisterEvent(
                    newClient,
                    new { Message = "Entrou na sala" },
                    null,
                    _room,
                    EventType.EnterRoom);

                return newClient;
            }
            catch (FullChatRoomException e)
            {
                WaitingClient waiter = new WaitingClient { Name = _sName, Email = _sEmail };
                // Set up a queue for this chat room, since it is currently full
                if (dictQueues.ContainsKey(_room.Name))
                {
                    // The queue already exists. Add the client to it.
                    dictQueues[_room.Name].Enqueue(waiter);
                }
                else
                {
                    // The queue does not exist (is empty). 
                    // Let's initialize it with a queue containing this single client
                    Queue<WaitingClient> roomQueue = new Queue<WaitingClient>();
                    roomQueue.Enqueue(waiter);
                    dictQueues.Add(_room.Name, roomQueue);
                }

                // Re-throw the exception and let the client know his position on the queue
                throw new FullChatRoomException(e.Message, dictQueues[_room.Name].Count);
            }
        }

        public static void RemoveClient(
            CTChatRoom _room, 
            CTClient _clientLeaving)
        {
            if (dictQueues.ContainsKey(_room.Name))
            {
                Queue<WaitingClient> roomQueue = dictQueues[_room.Name];

                if (roomQueue.FirstOrDefault(clnt => clnt.Email == _clientLeaving.Email) != null)
                {
                    // found the client in one of the room queues. 
                    // Just remove him from the queue and we're all set!!
                    dictQueues[_room.Name] = RemoveWaitingClient(roomQueue, _clientLeaving.Email);
                    return;
                }
            }
            
            RemoveClient(_room, _clientLeaving.Email);
        }

        private static Queue<WaitingClient> RemoveWaitingClient(Queue<WaitingClient> _queueSource, string _sEmail)
        {
            Queue<WaitingClient> retVal = new Queue<WaitingClient>(_queueSource.Count - 1);

            for (int i = 0; i < _queueSource.Count; i++)
            {
                WaitingClient clnt = _queueSource.ElementAt(i);
                if (clnt.Email != _sEmail)
                {
                    retVal.Enqueue(clnt);
                }
            }

            return retVal;
        }


        public static CTClient RemoveClient(
            CTChatRoom _room,
            string _sEmail)
        {
            CTClient clientLeaving = null;

            try
            {
                clientLeaving = _room.RemoveClient(_sEmail);
            }
            catch (InvalidOperationException)
            {
                // search in the room queue, if any
            }

            // If there is a queue for this room, let the first client
            // in the queue know that he will be able to join now
            if (dictQueues.ContainsKey(_room.Name))
            {
                Queue<WaitingClient> roomQueue = dictQueues[_room.Name];

                if (roomQueue.Count > 0)
                {
                    WaitingClient nextInQueue = roomQueue.Dequeue();

                    // add the waiting client to the room
                    AddClient(_room, nextInQueue.Name, nextInQueue.Email, RoleTypes.Member);

                    // The leaving client sends a message to the next client 
                    // in line, notifying that there is a position available in 
                    // the requested room.
                    clientLeaving.RegisterEvent(
                        new { Room = _room.Name }, 
                        nextInQueue.Email, 
                        EventType.RoomSpaceAvailable,
                        _room);

                    SignalClient(nextInQueue.Email);

                    // notify other waiting clients that the queue size has reduced
                    roomQueue.ToList().ForEach(item =>
                        {
                            clientLeaving.RegisterEvent(
                                new { Room = _room.Name, QueueSize = roomQueue.Count },
                                item.Email, 
                                EventType.QueueSizeChanged,
                                _room);

                            SignalClient(item.Email);
                        });
                }
            }

            RegisterEvent(
                clientLeaving,
                new { Message = "Saiu da sala" },
                null,
                _room,
                EventType.LeaveRoom);

            return clientLeaving;
        }

        public static void ClearClients(CTChatRoom _room)
        {
            _room.ClearClients();
        }

        #endregion

        #region Client operations

        public static void SendMessage(
            CTClient _client,
            string _sText,
            string _sRecipientEmail,
            CTChatRoom _room)
        {
            RegisterEvent(
                _client,
                new { Message = _sText },
                _sRecipientEmail,
                _room,
                EventType.Message);
        }

        public static void SendMessage(
            CTClient _client,
            string _sText,
            CTChatRoom _room)
        {
            RegisterEvent(
                _client,
                new { Message = _sText },
                null,
                _room,
                EventType.Message);
        }

        private static void RegisterEvent(
            CTClient _client,
            object _msg,
            string _sRecipientEmail,
            CTChatRoom _room,
            EventType _eType)
        {
            if (_sRecipientEmail != null)
            {
                _client.RegisterEvent(_msg, _sRecipientEmail, _eType, _room);
                SignalClient(_sRecipientEmail);
            }
            else
            {
                _client.RegisterEvent(_msg, _eType, _room);

                _room.ClientList.ForEach(c =>
                {
                    if (c.Email != _client.Email)
                    {
                        SignalClient(c.Email);
                    }
                });
            }
        }

        public static List<CTEvent> Receive(CTClient _client)
        {
            // the AutoReset event is auto-reset when the waiting thread is released
            return _client.Receive();
        }

        #endregion
    }
}